在這次鐵人賽的挑戰中,主要的目標是希望自己和讀者們可以更加認識 React.js 或者是複習一下以前學過的知識,所以安排了幾個單元來進行介紹,預期介紹的單元如下:
註: 本系列挑戰文會全部以 functional component 去做介紹
那在進入 React hooks 單元前,先來了解一下什麼是 hook?為什麼要使用 Hook?
在 React.js 的官網提到
Hook 是 React 16.8 中增加的新功能。它讓你不必寫 class 就能使用 state 以及其他 React 的功能。
回顧 React 的發展過程,從 class component 逐漸轉向使用 function component 為主,可以讓我們寫程式的邏輯可以不用再和生命週期函式綁定在一起,在設計上更有彈性,而且 class component 必須處理 this 綁定,這對初學者來說常常造成困惑,且可能引發錯誤。function components 不使用 this,消除了這個問題。
但純粹的 function component 本身要渲染到網頁畫面上時無法去控制和記憶狀態,所以這個部分就透過 hooks 去做補足,換句話說就是讓 functional component 能夠擁有狀態,然後 functional component 和 React hooks 都以函式的形式撰寫,這樣的好處是開發者可以自由的定義函式的傳入參數和回傳值,在設計程式碼上更有彈性。
在過去,使用 class component 時需要使用 HOC(Higher Order Component) 與 Render Props 去進行 Code Reuse,但多層的 HOC 會造成 wrapper hell,維護性不佳,並且多層的 HOC 也可能會有 Props 命名衝突的問題。
class component 很多的程式碼也都和生命週期函式做綁定,如果有重複性邏輯會不好抽取出來。
但我們現在可以將和某個小功能相關的程式碼邏輯放在同一個 custom hook 裡面,讓不同的元件需要該小功能時,就可以使用該 hook,減少程式碼的重複性,也更方便維護該功能。
每次呼叫一個 Hook 時,都會有一個完全獨立且隔離的 state,所以你甚至可以在一個 component 使用同一個客製化 Hook 兩次
看起來 hook 確實很強大,但它也有些使用上的限制:
這邊可以和 Day29 Fiber 段落的內容一起觀看,我們如果在元件內部使用了多個 hooks,它們的相關狀態、資料等會形成一個 linked list,每次渲染時就會依序照著這個 linked list 去執行 hook。
這樣設計的好處是 hooks 在多個元件複用時,可以不用擔心會有重複命名的問題,而是透過像是索引的方式去區別各個的 hooks,因此如果在條件式、迴圈內部或是巢狀函式等地方使用到 hooks,每次渲染就有可能不會執行到某些 hooks 進而導致順序錯亂。
在 React hooks: not magic, just arrays 這篇文章的 And the naive implementation 段落,有模擬一個簡化版本的 useState,作者提到這不是用來表達 hook 底層的運作方式,但簡化版本的 useState 可以更容易讓你了解為什麼 React hook 只能在 function component 裡的頂層調用的原因。
在範例程式碼中,使用了 state 和 setters 兩個陣列還有 cursor 計數的變數,而每使用一個 useState hook,就會將 state 和 setters 兩個陣列推入新的元素,然後 cursor 加一。所以 state 會分別存 firstName 和 lastName 值,setters 存 setFirstName 和 setLastName,此時每次渲染元件時,useState 的回傳值就是透過 cursor 當作索引去分別取出 state 和 setters 兩個陣列的元素,索引值不對就會更新到不正確的 state,這樣就很容易理解為什麼 hooks 會有這個限制的原因了。
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
在我們了解 hook 是什麼及為什麼要使用它後,接著就來認識一下有哪些 hook 吧!